{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# Routing\n",
    "\n",
    "Optical and high-speed RF ports have orientation requirements to avoid sharp turns, which can cause signal reflections.\n",
    "\n",
    "GDSFactory offers:\n",
    "\n",
    "- `route_single`: Routes a single connection between two ports.\n",
    "- `route_bundle`: Routes multiple connections between two port groups using a bundle/river/bus router. It also accommodates waypoints and routing steps.\n",
    "\n",
    "The most versatile function is `route_bundle`, as it handles both single and grouped routes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "from gdsfactory.component import Component\n",
    "from gdsfactory.port import Port"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "mmi1 = c << gf.components.mmi1x2()\n",
    "mmi2 = c << gf.components.mmi1x2()\n",
    "mmi2.move((100, 50))\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3",
   "metadata": {},
   "source": [
    "## route_single\n",
    "\n",
    "`route_single` returns a Manhattan route between 2 ports. A Manhattan route is a path between two points that is made up of only horizontal and vertical segments, with all turns being 90-degree angles."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4",
   "metadata": {},
   "outputs": [],
   "source": [
    "help(gf.routing.route_single)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "mmi1 = c << gf.components.mmi1x2()\n",
    "mmi2 = c << gf.components.mmi1x2()\n",
    "mmi2.move((100, 50))\n",
    "route = gf.routing.route_single(\n",
    "    c,\n",
    "    port1=mmi1.ports[\"o2\"],\n",
    "    port2=mmi2.ports[\"o1\"],\n",
    "    cross_section=gf.cross_section.strip,\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6",
   "metadata": {},
   "outputs": [],
   "source": [
    "c.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7",
   "metadata": {},
   "source": [
    "⚠️ **Note:** You can also get the route length, but keep the following in mind: That route length is in **DBU** (Database Units). Usually, **1 DBU = 1 nm**.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(f\"route length = {route.length} DBU, {route.length/1000} um\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9",
   "metadata": {},
   "outputs": [],
   "source": [
    "route_length = 0\n",
    "for instance in route.instances:\n",
    "    route_length += instance.cell.info['length']\n",
    "\n",
    "print(f\"total route length = {route_length} um\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "mmi1 = c << gf.components.mmi1x2()\n",
    "mmi2 = c << gf.components.mmi1x2()\n",
    "mmi2.move((100, 50))\n",
    "route = gf.routing.route_single(\n",
    "    c, port1=mmi1.ports[\"o2\"], port2=mmi2.ports[\"o1\"], layer=(1, 0), route_width=2\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11",
   "metadata": {},
   "source": [
    "**Problem**: route_single with obstacles\n",
    "\n",
    "Sometimes there are obstacles that the connect strip does not see!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "mmi1 = c << gf.components.mmi1x2()\n",
    "mmi2 = c << gf.components.mmi1x2()\n",
    "mmi2.move((110, 50))\n",
    "x = c << gf.components.cross(length=20)\n",
    "x.move((135, 20))\n",
    "route = gf.routing.route_single(\n",
    "    c, mmi1.ports[\"o2\"], mmi2.ports[\"o2\"], cross_section=\"strip\"\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13",
   "metadata": {},
   "source": [
    "**Solution**: specify the route steps\n",
    "\n",
    "`route_single` allows you to define relative or absolute steps `x` or `y` for the route as well as with increments `dx` or `dy`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "14",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "w = gf.components.straight()\n",
    "left = c << w\n",
    "right = c << w\n",
    "right.move((100, 80))\n",
    "\n",
    "obstacle = gf.components.rectangle(size=(100, 10))\n",
    "obstacle1 = c << obstacle\n",
    "obstacle2 = c << obstacle\n",
    "obstacle1.ymin = 40\n",
    "obstacle2.xmin = 25\n",
    "\n",
    "port1 = left.ports[\"o2\"]\n",
    "port2 = right.ports[\"o2\"]\n",
    "\n",
    "routes = gf.routing.route_single(\n",
    "    c,\n",
    "    port1=port1,\n",
    "    port2=port2,\n",
    "    cross_section=\"strip\",\n",
    "\n",
    "    # This provides a list of explicit waypoints (x, y coordinates) for the router. Instead of finding a path automatically,\n",
    "    # the router is forced to create a Manhattan route that passes through each of these points in sequence.\n",
    "    # This gives the user full, manual control over the waveguide's path.\n",
    "    steps=[\n",
    "        {\"x\": 20, \"y\": 0},\n",
    "        {\"x\": 20, \"y\": 20},\n",
    "        {\"x\": 120, \"y\": 20},\n",
    "        {\"x\": 120, \"y\": 80},\n",
    "    ],\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "15",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "w = gf.components.straight()\n",
    "left = c << w\n",
    "right = c << w\n",
    "right.move((100, 80))\n",
    "\n",
    "obstacle = gf.components.rectangle(size=(100, 10))\n",
    "obstacle1 = c << obstacle\n",
    "obstacle2 = c << obstacle\n",
    "obstacle1.ymin = 40\n",
    "obstacle2.xmin = 25\n",
    "\n",
    "port1 = left.ports[\"o2\"]\n",
    "port2 = right.ports[\"o2\"]\n",
    "\n",
    "routes = gf.routing.route_single(\n",
    "    c,\n",
    "    port1=port1,\n",
    "    port2=port2,\n",
    "    cross_section=\"strip\",\n",
    "    steps=[\n",
    "        {\"x\": 20},\n",
    "        {\"y\": 20},\n",
    "        {\"x\": 120},\n",
    "        {\"y\": 80},\n",
    "    ],\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16",
   "metadata": {},
   "source": [
    "## route_bundle\n",
    "\n",
    "To route groups of ports avoiding routing collisions between each route, you should use `route_bundle` instead of `route_single`.\n",
    "\n",
    "`route_bundle` uses a river/bundle/bus router.\n",
    "\n",
    "At the moment it works only when each group of ports has the same orientation.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17",
   "metadata": {},
   "outputs": [],
   "source": [
    "ys_right = [0, 10, 20, 40, 50, 80] # This line creates a list of six explicit y-coordinates for the right-side ports.\n",
    "pitch = 127.0 # Defines the constant vertical distance for the left side ports.\n",
    "N = len(ys_right) # This defines the total number of ports in each column (N, which is 6).\n",
    "\n",
    "# This line uses a list comprehension to calculate six y-coordinates for the left-side ports.\n",
    "# The formula (i - N / 2) * pitch ensures that the ports are evenly spaced by 127.0 µm and are centered vertically around y=0.\n",
    "ys_left = [(i - N / 2) * pitch for i in range(N)]\n",
    "layer = (1, 0)\n",
    "\n",
    "right_ports = [\n",
    "    gf.Port(f\"R_{i}\", center=(0, ys_right[i]), width=0.5, orientation=180, layer=gf.get_layer(layer))\n",
    "    for i in range(N)\n",
    "]\n",
    "left_ports = [\n",
    "    gf.Port(f\"L_{i}\", center=(-200, ys_left[i]), width=0.5, orientation=0, layer=gf.get_layer(layer))\n",
    "    for i in range(N)\n",
    "]\n",
    "\n",
    "# You can also mess up the port order and it will sort them by default.\n",
    "left_ports.reverse()\n",
    "\n",
    "c = gf.Component()\n",
    "routes = gf.routing.route_bundle(\n",
    "    c,\n",
    "    left_ports,\n",
    "    right_ports,\n",
    "    start_straight_length=50,\n",
    "    sort_ports=True,\n",
    "    cross_section=\"strip\",\n",
    ")\n",
    "c.add_ports(left_ports)\n",
    "c.add_ports(right_ports)\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18",
   "metadata": {},
   "outputs": [],
   "source": [
    "xs_top = [0, 10, 20, 40, 50, 80]\n",
    "pitch = 127.0\n",
    "N = len(xs_top)\n",
    "xs_bottom = [(i - N / 2) * pitch for i in range(N)]\n",
    "layer = (1, 0)\n",
    "\n",
    "top_ports = [\n",
    "    gf.Port(f\"top_{i}\", center=(xs_top[i], 0), width=0.5, orientation=270, layer=gf.get_layer(layer))\n",
    "    for i in range(N)\n",
    "]\n",
    "\n",
    "bot_ports = [\n",
    "    gf.Port(\n",
    "        f\"bot_{i}\",\n",
    "        center=(xs_bottom[i], -300),\n",
    "        width=0.5,\n",
    "        orientation=90,\n",
    "        layer=gf.get_layer(layer),\n",
    "    )\n",
    "    for i in range(N)\n",
    "]\n",
    "\n",
    "c = gf.Component()\n",
    "routes = gf.routing.route_bundle(\n",
    "    c,\n",
    "    top_ports,\n",
    "    bot_ports,\n",
    "    separation=5.0,\n",
    "    end_straight_length=100,\n",
    "    cross_section=\"strip\",\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "source": [
    "`route_bundle` can also route bundles through corners:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# The ports are grouped by quadrant: Top-Right (TR), Top-Left (TL), Bottom-Right (BR), and Bottom-Left (BL).\n",
    "# Ports_A: These are groups of N ports arranged in vertical lines on the left and right sides of the component.\n",
    "# Their orientation is either 0 (facing right) or 180 (facing left).\n",
    "# Ports_B: These are groups of N ports arranged in horizontal lines on the top and bottom of the component.\n",
    "# Their orientation is either 90 (facing up) or 270 (facing down).\n",
    "@gf.cell(cache={})\n",
    "def test_connect_corner(N=6, config=\"A\"):\n",
    "    d = 10.0\n",
    "    sep = 5.0\n",
    "    c = gf.Component()\n",
    "    layer = (1, 0)\n",
    "\n",
    "    if config in [\"A\", \"B\"]:\n",
    "        a = 100.0\n",
    "        ports_A_TR = [\n",
    "            Port(\n",
    "                f\"A_TR_{i}\",\n",
    "                center=(d, a / 2 + i * sep), # d, sep: Geometric values defining the distance and separation between ports.\n",
    "                width=0.5,\n",
    "                orientation=0,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports_A_TL = [\n",
    "            Port(\n",
    "                f\"A_TL_{i}\",\n",
    "                center=(-d, a / 2 + i * sep),\n",
    "                width=0.5,\n",
    "                orientation=180,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports_A_BR = [\n",
    "            Port(\n",
    "                f\"A_BR_{i}\",\n",
    "                center=(d, -a / 2 - i * sep),\n",
    "                width=0.5,\n",
    "                orientation=0,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports_A_BL = [\n",
    "            Port(\n",
    "                f\"A_BL_{i}\",\n",
    "                center=(-d, -a / 2 - i * sep),\n",
    "                width=0.5,\n",
    "                orientation=180,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports_A = [ports_A_TR, ports_A_TL, ports_A_BR, ports_A_BL]\n",
    "\n",
    "        ports_B_TR = [\n",
    "            Port(\n",
    "                f\"B_TR_{i}\",\n",
    "                center=(a / 2 + i * sep, d),\n",
    "                width=0.5,\n",
    "                orientation=90,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports_B_TL = [\n",
    "            Port(\n",
    "                f\"B_TL_{i}\",\n",
    "                center=(-a / 2 - i * sep, d),\n",
    "                width=0.5,\n",
    "                orientation=90,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports_B_BR = [\n",
    "            Port(\n",
    "                f\"B_BR_{i}\",\n",
    "                center=(a / 2 + i * sep, -d),\n",
    "                width=0.5,\n",
    "                orientation=270,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports_B_BL = [\n",
    "            Port(\n",
    "                f\"B_BL_{i}\",\n",
    "                center=(-a / 2 - i * sep, -d),\n",
    "                width=0.5,\n",
    "                orientation=270,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports_B = [ports_B_TR, ports_B_TL, ports_B_BR, ports_B_BL]\n",
    "\n",
    "    elif config in [\"C\", \"D\"]:\n",
    "        a = N * sep + 2 * d\n",
    "        ports_A_TR = [\n",
    "            Port(\n",
    "                f\"A_TR_{i}\",\n",
    "                center=(a, d + i * sep),\n",
    "                width=0.5,\n",
    "                orientation=0,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports_A_TL = [\n",
    "            Port(\n",
    "                f\"A_TL_{i}\",\n",
    "                center=(-a, d + i * sep),\n",
    "                width=0.5,\n",
    "                orientation=180,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports_A_BR = [\n",
    "            Port(\n",
    "                f\"A_BR_{i}\",\n",
    "                center=(a, -d - i * sep),\n",
    "                width=0.5,\n",
    "                orientation=0,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports_A_BL = [\n",
    "            Port(\n",
    "                f\"A_BL_{i}\",\n",
    "                center=(-a, -d - i * sep),\n",
    "                width=0.5,\n",
    "                orientation=180,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports_A = [ports_A_TR, ports_A_TL, ports_A_BR, ports_A_BL]\n",
    "\n",
    "        ports_B_TR = [\n",
    "            Port(\n",
    "                f\"B_TR_{i}\",\n",
    "                center=(d + i * sep, a),\n",
    "                width=0.5,\n",
    "                orientation=90,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports_B_TL = [\n",
    "            Port(\n",
    "                f\"B_TL_{i}\",\n",
    "                center=(-d - i * sep, a),\n",
    "                width=0.5,\n",
    "                orientation=90,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports_B_BR = [\n",
    "            Port(\n",
    "                f\"B_BR_{i}\",\n",
    "                center=(d + i * sep, -a),\n",
    "                width=0.5,\n",
    "                orientation=270,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports_B_BL = [\n",
    "            Port(\n",
    "                f\"B_BL_{i}\",\n",
    "                center=(-d - i * sep, -a),\n",
    "                width=0.5,\n",
    "                orientation=270,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports_B = [ports_B_TR, ports_B_TL, ports_B_BR, ports_B_BL]\n",
    "\n",
    "    # This loop iterates through the corresponding groups of ports from sets A and B\n",
    "    # (e.g., it pairs ports_A_TR with ports_B_TR, then ports_A_TL with ports_B_TL.\n",
    "    if config in [\"A\", \"C\"]:\n",
    "        for ports1, ports2 in zip(ports_A, ports_B):\n",
    "            gf.routing.route_bundle(\n",
    "                c, ports1, ports2, radius=5, sort_ports=True, cross_section=\"strip\"\n",
    "            )\n",
    "\n",
    "    elif config in [\"B\", \"D\"]:\n",
    "        for ports1, ports2 in zip(ports_A, ports_B):\n",
    "            gf.routing.route_bundle(\n",
    "                c, ports2, ports1, radius=5, sort_ports=True, cross_section=\"strip\"\n",
    "            )\n",
    "\n",
    "    return c\n",
    "\n",
    "\n",
    "c = test_connect_corner(config=\"A\")\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = test_connect_corner(config=\"C\")\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22",
   "metadata": {},
   "outputs": [],
   "source": [
    "@gf.cell(cache={})\n",
    "\n",
    "# dy=200: This defines the distance or separation between the two groups of ports.\n",
    "def test_connect_bundle_udirect(dy=200, orientation=270, layer=(1, 0)):\n",
    "    xs1 = [-100, -90, -80, -55, -35, 24, 0] + [200, 210, 240]\n",
    "    axis = \"X\" if orientation in [0, 180] else \"Y\"\n",
    "    pitch = 10.0\n",
    "    N = len(xs1)\n",
    "    xs2 = [70 + i * pitch for i in range(N)]\n",
    "\n",
    "    # This determines the layout:\n",
    "    # If the orientation is horizontal (0° or 180°), the separation dy will be along the X-axis.\n",
    "    # If the orientation is vertical (90° or 270°), the separation dy will be along the Y-axis.\n",
    "    # ports1 are created in a vertical line where x=0 and y coordinates are taken from the uneven xs1 list.\n",
    "    # ports2 are created in a parallel vertical line where x=dy (shifted by 200) and y coordinates are from the evenly spaced xs2 list.\n",
    "    # This results in two vertical lines of ports which are separated horizontally.\n",
    "    if axis == \"X\":\n",
    "        ports1 = [\n",
    "            Port(\n",
    "                f\"top_{i}\",\n",
    "                center=(0, xs1[i]),\n",
    "                width=0.5,\n",
    "                orientation=orientation,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports2 = [\n",
    "            Port(\n",
    "                f\"bottom_{i}\",\n",
    "                center=(dy, xs2[i]),\n",
    "                width=0.5,\n",
    "                orientation=orientation,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "    else:\n",
    "        ports1 = [\n",
    "            Port(\n",
    "                f\"top_{i}\",\n",
    "                center=(xs1[i], 0),\n",
    "                width=0.5,\n",
    "                orientation=orientation,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports2 = [\n",
    "            Port(\n",
    "                f\"bottom_{i}\",\n",
    "                center=(xs2[i], dy),\n",
    "                width=0.5,\n",
    "                orientation=orientation,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "    c = Component()\n",
    "    gf.routing.route_bundle(\n",
    "        c, ports1, ports2, radius=10.0, sort_ports=True, cross_section=\"strip\"\n",
    "    )\n",
    "    return c\n",
    "\n",
    "\n",
    "c = test_connect_bundle_udirect()\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23",
   "metadata": {},
   "outputs": [],
   "source": [
    "@gf.cell\n",
    "def test_connect_bundle_u_indirect(dy=-200, orientation=180, layer=(1, 0)):\n",
    "    xs1 = [-100, -90, -80, -55, -35] + [200, 210, 240]\n",
    "    axis = \"X\" if orientation in [0, 180] else \"Y\"\n",
    "    pitch = 10.0\n",
    "    N = len(xs1)\n",
    "    xs2 = [50 + i * pitch for i in range(N)]\n",
    "\n",
    "    a1 = orientation\n",
    "    a2 = a1 + 180\n",
    "\n",
    "    if axis == \"X\":\n",
    "        ports1 = [\n",
    "            Port(f\"top_{i}\", center=(0, xs1[i]), width=0.5, orientation=a1, layer=gf.get_layer(layer))\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports2 = [\n",
    "            Port(\n",
    "                f\"bot_{i}\",\n",
    "                center=(dy, xs2[i]),\n",
    "                width=0.5,\n",
    "                orientation=a2,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "    else:\n",
    "        ports1 = [\n",
    "            Port(f\"top_{i}\", center=(xs1[i], 0), width=0.5, orientation=a1, layer=gf.get_layer(layer))\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "        ports2 = [\n",
    "            Port(\n",
    "                f\"bot_{i}\",\n",
    "                center=(xs2[i], dy),\n",
    "                width=0.5,\n",
    "                orientation=a2,\n",
    "                layer=gf.get_layer(layer),\n",
    "            )\n",
    "            for i in range(N)\n",
    "        ]\n",
    "\n",
    "    c = Component()\n",
    "    gf.routing.route_bundle(\n",
    "        c,\n",
    "        ports1,\n",
    "        ports2,\n",
    "        bend=gf.components.bend_euler,\n",
    "        radius=5,\n",
    "        cross_section=\"strip\",\n",
    "    )\n",
    "\n",
    "    return c\n",
    "\n",
    "\n",
    "c = test_connect_bundle_u_indirect(orientation=0)\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24",
   "metadata": {},
   "outputs": [],
   "source": [
    "@gf.cell\n",
    "def test_north_to_south(layer=(1, 0)):\n",
    "    dy = 200.0\n",
    "    xs1 = [-500, -300, -100, -90, -80, -55, -35, 200, 210, 240, 500, 650]\n",
    "\n",
    "    pitch = 10.0\n",
    "    N = len(xs1)\n",
    "    xs2 = [-20 + i * pitch for i in range(N // 2)]\n",
    "    xs2 += [400 + i * pitch for i in range(N // 2)]\n",
    "\n",
    "    a1 = 90\n",
    "    a2 = a1 + 180\n",
    "\n",
    "    ports1 = [\n",
    "        gf.Port(f\"top_{i}\", center=(xs1[i], 0), width=0.5, orientation=a1, layer=gf.get_layer(layer))\n",
    "        for i in range(N)\n",
    "    ]\n",
    "\n",
    "    ports2 = [\n",
    "        gf.Port(f\"bot_{i}\", center=(xs2[i], dy), width=0.5, orientation=a2, layer=gf.get_layer(layer))\n",
    "        for i in range(N)\n",
    "    ]\n",
    "\n",
    "    c = gf.Component()\n",
    "    gf.routing.route_bundle(c, ports1, ports2, cross_section=\"strip\")\n",
    "    return c\n",
    "\n",
    "\n",
    "c = test_north_to_south()\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25",
   "metadata": {},
   "outputs": [],
   "source": [
    "@gf.cell\n",
    "def demo_connect_bundle():\n",
    "    \"\"\"Combines all the connect_bundle tests.\"\"\"\n",
    "    y = 400.0\n",
    "    x = 500\n",
    "    y0 = 900\n",
    "    dy = 200.0\n",
    "    c = gf.Component()\n",
    "    for j, s in enumerate([-1, 1]):\n",
    "        for i, orientation in enumerate([0, 90, 180, 270]):\n",
    "            ref = c << test_connect_bundle_u_indirect(\n",
    "                dy=s * dy, orientation=orientation\n",
    "            )\n",
    "            ref.dcenter = (i * x, j * y)\n",
    "\n",
    "            ref = c << test_connect_bundle_udirect(dy=s * dy, orientation=orientation)\n",
    "            ref.dcenter = (i * x, j * y + y0)\n",
    "\n",
    "    for i, config in enumerate([\"A\", \"B\", \"C\", \"D\"]):\n",
    "        ref = c << test_connect_corner(config=config)\n",
    "        ref.dcenter = (i * x, 1700)\n",
    "\n",
    "    return c\n",
    "\n",
    "\n",
    "c = demo_connect_bundle()\n",
    "c.show()\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "27",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "c1 = c << gf.components.mmi2x2()\n",
    "c2 = c << gf.components.mmi2x2()\n",
    "\n",
    "c2.move((100, 50))\n",
    "routes = gf.routing.route_bundle(\n",
    "    c,\n",
    "    [c1.ports[\"o4\"], c1.ports[\"o3\"]],\n",
    "    [c2.ports[\"o1\"], c2.ports[\"o2\"]],\n",
    "    radius=5,\n",
    "    cross_section=\"strip\",\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "28",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "c1 = c << gf.components.pad()\n",
    "c2 = c << gf.components.pad()\n",
    "c2.move((200, 100))\n",
    "routes = gf.routing.route_bundle_electrical(\n",
    "    c,\n",
    "    [c1.ports[\"e3\"]],\n",
    "    [c2.ports[\"e1\"]],\n",
    "    cross_section=gf.cross_section.metal3,\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29",
   "metadata": {},
   "source": [
    "**Problem**\n",
    "\n",
    "Sometimes 90 degrees routes do not have enough space for a Manhattan route."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "30",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "c1 = c << gf.components.nxn(east=3, ysize=20)\n",
    "c2 = c << gf.components.nxn(west=3)\n",
    "c2.move((80, 0))\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "31",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "c1 = c << gf.components.nxn(east=3, ysize=20)\n",
    "c2 = c << gf.components.nxn(west=3)\n",
    "c2.move((80, 0))\n",
    "routes = gf.routing.route_bundle(\n",
    "    c,\n",
    "    list(c1.ports.filter(orientation=0)),\n",
    "    list(c2.ports.filter(orientation=180)),\n",
    "    on_collision=None,\n",
    "    cross_section=\"strip\",\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "32",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "pitch = 2.0\n",
    "ys_left = [0, 10, 20]\n",
    "N = len(ys_left)\n",
    "ys_right = [(i - N / 2) * pitch for i in range(N)]\n",
    "layer = (1, 0)\n",
    "\n",
    "right_ports = [\n",
    "    gf.Port(f\"R_{i}\", center=(0, ys_right[i]), width=0.5, orientation=180, layer=gf.get_layer(layer))\n",
    "    for i in range(N)\n",
    "]\n",
    "left_ports = [\n",
    "    gf.Port(f\"L_{i}\", center=(-50, ys_left[i]), width=0.5, orientation=0, layer=gf.get_layer(layer))\n",
    "    for i in range(N)\n",
    "]\n",
    "\n",
    "# This reverses the order of the items in the list named left_ports in-place.\n",
    "# For example, if left_ports was [port_A, port_B, port_C], after this line it would become [port_C, port_B, port_A].\n",
    "# on_collision=None: This tells the router how to handle cases where waveguides might overlap or run into other geometry.\n",
    "# By setting it to None, the router will simply ignore any collisions.\n",
    "left_ports.reverse()\n",
    "routes = gf.routing.route_bundle(\n",
    "    c, right_ports, left_ports, radius=5, on_collision=None, cross_section=\"strip\"\n",
    ")\n",
    "\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "33",
   "metadata": {},
   "source": [
    "**Solution**\n",
    "\n",
    "Add S-bend routes using `route_bundle_sbend`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "c1 = c << gf.components.nxn(east=3, ysize=20)\n",
    "c2 = c << gf.components.nxn(west=3)\n",
    "c2.move((80, 0))\n",
    "routes = gf.routing.route_bundle_sbend(\n",
    "    c,\n",
    "    c1.ports.filter(orientation=0),\n",
    "    c2.ports.filter(orientation=180),\n",
    "    enforce_port_ordering=False,\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "35",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "from gdsfactory.samples.big_device import big_device\n",
    "\n",
    "c = gf.Component()\n",
    "c1 = big_device()\n",
    "c2 = gf.routing.add_fiber_array(c1)\n",
    "c2.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "w = gf.components.straight()\n",
    "left = c << w\n",
    "right = c << w\n",
    "right.move((100, 80))\n",
    "\n",
    "obstacle = gf.components.rectangle(size=(100, 10))\n",
    "obstacle1 = c << obstacle\n",
    "obstacle2 = c << obstacle\n",
    "obstacle1.ymin = 40\n",
    "obstacle2.xmin = 25\n",
    "\n",
    "port1 = left.ports[\"o2\"]\n",
    "port2 = right.ports[\"o2\"]\n",
    "\n",
    "routes = gf.routing.route_bundle(\n",
    "    c,\n",
    "    [port1],\n",
    "    [port2],\n",
    "    cross_section=\"strip\",\n",
    "    steps=[\n",
    "        {\"dy\": 30, \"dx\": 50},\n",
    "        {\"dx\": 100},\n",
    "    ],\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "37",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "w = gf.components.array(gf.c.straight, columns=1, rows=3, row_pitch=3)\n",
    "left = c << w\n",
    "right = c << w\n",
    "right.move((100, 100))\n",
    "\n",
    "obstacle = gf.components.rectangle(size=(100, 10))\n",
    "obstacle1 = c << obstacle\n",
    "obstacle2 = c << obstacle\n",
    "obstacle1.ymin = 40\n",
    "obstacle2.xmin = 35\n",
    "\n",
    "ports1 = left.ports.filter(orientation=0)\n",
    "ports2 = right.ports.filter(orientation=180)\n",
    "\n",
    "routes = gf.routing.route_bundle(\n",
    "    c,\n",
    "    ports1,\n",
    "    ports2,\n",
    "    cross_section=\"strip\",\n",
    "    sort_ports=True,\n",
    "    steps=[\n",
    "        {\"dy\": 30, \"dx\": 50},\n",
    "        {\"dx\": 90},\n",
    "    ],\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "38",
   "metadata": {},
   "source": [
    "## route_astar\n",
    "\n",
    "You can navigate around bounding boxes when routing if you pass the boxes of all the objects that you want to avoid.\n",
    "\n",
    "\n",
    "If you want to navigate around all bounding boxes, you can also use the `route_astar` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "\n",
    "c = gf.Component()\n",
    "cross_section = \"strip\"\n",
    "port_prefix = \"o\"\n",
    "bend = gf.components.bend_euler\n",
    "\n",
    "cross_section = gf.get_cross_section(cross_section, radius=5)\n",
    "w = gf.components.straight(cross_section=cross_section)\n",
    "left = c << w\n",
    "right = c << w\n",
    "right.rotate(90)\n",
    "right.move((168, 63))\n",
    "\n",
    "obstacle = gf.components.rectangle(size=(250, 3), layer=\"M2\")\n",
    "obstacle1 = c << obstacle\n",
    "obstacle2 = c << obstacle\n",
    "obstacle3 = c << obstacle\n",
    "obstacle4 = c << obstacle\n",
    "obstacle4.rotate(90)\n",
    "obstacle1.ymin = 50\n",
    "obstacle1.xmin = -10\n",
    "obstacle2.xmin = 35\n",
    "obstacle3.ymin = 42\n",
    "obstacle3.xmin = 72.23\n",
    "obstacle4.xmin = 200\n",
    "obstacle4.ymin = 55\n",
    "port1 = left.ports[f\"{port_prefix}1\"]\n",
    "port2 = right.ports[f\"{port_prefix}2\"]\n",
    "\n",
    "# This gdsfactory function calculates a route for a waveguide using the A* algorithm,\n",
    "# which is a popular pathfinding algorithm known for finding the shortest path between two points on a grid.\n",
    "route = gf.routing.route_astar(\n",
    "    component=c,\n",
    "    port1=port1,\n",
    "    port2=port2,\n",
    "    cross_section=cross_section,\n",
    "\n",
    "    # resolution=15: This parameter controls the grid size for the A* search algorithm. The router converts the component's layout into a grid,\n",
    "    # and a value of 15 means the grid cells are 15 nanometers on each side (since gdsfactory's default unit is micrometers, this corresponds to 0.015 um).\n",
    "    # A smaller resolution creates a finer grid, which can find more complex paths but takes longer to compute.\n",
    "    resolution=15,\n",
    "    distance=12,\n",
    "    avoid_layers=(\"M2\",),\n",
    "    bend=bend,\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "40",
   "metadata": {},
   "outputs": [],
   "source": [
    "@gf.cell\n",
    "def demo_route_astar_electrical() -> gf.Component:\n",
    "    c = gf.Component()\n",
    "    cross_section_name = \"metal_routing\"\n",
    "    port_prefix = \"e\"\n",
    "    bend = gf.components.wire_corner\n",
    "\n",
    "    cross_section = gf.get_cross_section(cross_section_name)\n",
    "    w = gf.components.straight(cross_section=cross_section)\n",
    "    left = c << w\n",
    "    right = c << w\n",
    "    right.rotate(90)  # Type: ignore[arg-type]\n",
    "    right.move((168, 63))\n",
    "\n",
    "    obstacle = gf.components.rectangle(size=(250, 3), layer=\"M3\")\n",
    "    obstacle1 = c << obstacle\n",
    "    obstacle2 = c << obstacle\n",
    "    obstacle3 = c << obstacle\n",
    "    obstacle4 = c << obstacle\n",
    "    obstacle4.rotate(90)  # Type: ignore[arg-type]\n",
    "    obstacle1.ymin = 50\n",
    "    obstacle1.xmin = -10\n",
    "    obstacle2.xmin = 35\n",
    "    obstacle3.ymin = 42\n",
    "    obstacle3.xmin = 72.23  # Type: ignore\n",
    "    obstacle4.xmin = 200\n",
    "    obstacle4.ymin = 55\n",
    "    port1 = left.ports[f\"{port_prefix}1\"]\n",
    "    port2 = right.ports[f\"{port_prefix}2\"]\n",
    "\n",
    "    gf.routing.route_astar(\n",
    "        component=c,\n",
    "        port1=port1,\n",
    "        port2=port2,\n",
    "        cross_section=cross_section,\n",
    "        resolution=10,\n",
    "        distance=15,\n",
    "        avoid_layers=(\"M3\",),\n",
    "        bend=bend,\n",
    "    )\n",
    "    return c\n",
    "\n",
    "c = demo_route_astar_electrical()\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "41",
   "metadata": {},
   "source": [
    "## route_bundle with collisions\n",
    "\n",
    "\n",
    "The route bundle also supports avoiding obstacles.\n",
    "\n",
    "Currently the router only respects any (merged) bounding boxes which overlap with start or end port bundles."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "42",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "\n",
    "c = gf.Component()\n",
    "columns = 2\n",
    "ptop = c << gf.components.pad_array(columns=columns, port_orientation=270)\n",
    "pbot = c << gf.components.pad_array(port_orientation=270, columns=columns)\n",
    "ptop.movex(300)\n",
    "ptop.movey(300)\n",
    "\n",
    "obstacle = c << gf.c.rectangle(size=(300, 100), layer=\"M3\")\n",
    "obstacle.ymin = pbot.ymax - 10\n",
    "obstacle.xmin = pbot.xmax - 10\n",
    "\n",
    "\n",
    "routes = gf.routing.route_bundle_electrical(\n",
    "    c,\n",
    "    pbot.ports,\n",
    "    ptop.ports,\n",
    "    start_straight_length=100,\n",
    "    separation=20,\n",
    "    cross_section=\"metal_routing\",\n",
    "    bboxes=[\n",
    "        obstacle.bbox(),\n",
    "        pbot.bbox(),\n",
    "        ptop.bbox(),\n",
    "    ],  # Obstacles to avoid need to be touching the initial or end ports.\n",
    "    sort_ports=True,\n",
    ")\n",
    "\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43",
   "metadata": {},
   "source": [
    "If the obstacle does not intersect the initial or end ports, the router does not attempt to avoid it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "44",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "\n",
    "c = gf.Component()\n",
    "columns = 2\n",
    "ptop = c << gf.components.pad_array(columns=columns, port_orientation=270)\n",
    "pbot = c << gf.components.pad_array(port_orientation=270, columns=columns)\n",
    "ptop.movex(300)\n",
    "ptop.movey(300)\n",
    "\n",
    "obstacle = c << gf.c.rectangle(size=(300, 100), layer=\"M3\", centered=True)\n",
    "obstacle.ymin = pbot.ymax - 10\n",
    "obstacle.xmin = pbot.xmax + 10\n",
    "\n",
    "\n",
    "routes = gf.routing.route_bundle_electrical(\n",
    "    c,\n",
    "    pbot.ports,\n",
    "    ptop.ports,\n",
    "    start_straight_length=100,\n",
    "    separation=20,\n",
    "    cross_section=\"metal_routing\",\n",
    "    bboxes=[\n",
    "        obstacle.bbox(), \n",
    "        pbot.bbox(),\n",
    "        ptop.bbox(),\n",
    "    ],  \n",
    "    sort_ports=True,\n",
    ")\n",
    "\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "45",
   "metadata": {},
   "source": [
    "However you can make it larger in order to set an obstacle it needs to avoid."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "46",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "\n",
    "c = gf.Component()\n",
    "columns = 2\n",
    "ptop = c << gf.components.pad_array(columns=columns, port_orientation=270)\n",
    "pbot = c << gf.components.pad_array(port_orientation=270, columns=columns)\n",
    "ptop.movex(300)\n",
    "ptop.movey(300)\n",
    "\n",
    "obstacle = c << gf.c.rectangle(size=(300, 100), layer=\"M3\", centered=True)\n",
    "obstacle.ymin = pbot.ymax - 10\n",
    "obstacle.xmin = pbot.xmax + 10\n",
    "\n",
    "\n",
    "routes = gf.routing.route_bundle_electrical(\n",
    "    c,\n",
    "    pbot.ports,\n",
    "    ptop.ports,\n",
    "    start_straight_length=100,\n",
    "    separation=20,\n",
    "    cross_section=\"metal_routing\",\n",
    "    bboxes=[\n",
    "        obstacle.bbox().enlarge(10), # Otherwise you need to enlarge the bbox by 10 um.\n",
    "        pbot.bbox(),\n",
    "        ptop.bbox(),\n",
    "    ],  \n",
    "    sort_ports=True,\n",
    ")\n",
    "\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "47",
   "metadata": {},
   "source": [
    "## route_bundle_all_angle\n",
    "\n",
    "You can also route using diagonal routes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "48",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "\n",
    "c = gf.Component()\n",
    "rows = 3\n",
    "straight = gf.c.straight\n",
    "w1 = c << gf.c.array(straight, rows=rows, columns=1, row_pitch=10)\n",
    "w2 = c << gf.c.array(straight, rows=rows, columns=1, row_pitch=10)\n",
    "w2.rotate(-30)\n",
    "w2.movex(140)\n",
    "p1 = list(w1.ports.filter(orientation=0))\n",
    "p2 = list(w2.ports.filter(orientation=150))\n",
    "p1.reverse()\n",
    "p2.reverse()\n",
    "\n",
    "gf.routing.route_bundle_all_angle(\n",
    "    c,\n",
    "    p1,\n",
    "    p2,\n",
    "    separation=3,\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "49",
   "metadata": {},
   "source": [
    "You can also use it to connect rotated components that do not have a manhattan orientation (0, 90, 180, 270)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "\n",
    "mmi = gf.components.mmi2x2(width_mmi=10, gap_mmi=3)\n",
    "mmi1 = c.add_ref_off_grid(mmi)  # Create a virtual instance.\n",
    "mmi2 = c.add_ref_off_grid(mmi)  # Create a virtual instance.\n",
    "\n",
    "mmi2.move((100, 10))\n",
    "mmi2.rotate(30)\n",
    "\n",
    "routes = gf.routing.route_bundle_all_angle(\n",
    "    c,\n",
    "    mmi1.ports.filter(orientation=0),\n",
    "    [mmi2.ports[\"o2\"], mmi2.ports[\"o1\"]],\n",
    ")\n",
    "c.show()\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51",
   "metadata": {},
   "source": [
    "## Dubins paths\n",
    "\n",
    "If you are looking for a straightforward way to optimize waveguide paths, Dubins paths (named after Lester Eli Dubins) offer an effective solution by ensuring the shortest path with minimal bending and loss.\n",
    "\n",
    "Using Dubins paths for waveguide routing can simplify your design process significantly.\n",
    "Compared to traditional interconnects, Dubins paths offer shorter, more reliable routes that avoid unnecessary bending and intersections.\n",
    "This translates into denser, cleaner designs with improved performance.\n",
    "\n",
    "[See blog](https://quentinwach.com/blog/2024/02/15/dubins-paths-for-waveguide-routing.html)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "52",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "\n",
    "# Create two straight waveguides with different orientations.\n",
    "wg1 = c << gf.components.straight(length=100, width=3.2)\n",
    "wg2 = c << gf.components.straight(length=100, width=3.2)\n",
    "\n",
    "# Move and rotate the second waveguide.\n",
    "wg2.move((300, 50))\n",
    "wg2.rotate(45)\n",
    "\n",
    "# Route between the output of wg1 and input of wg2.\n",
    "route = gf.routing.route_dubins(\n",
    "    c,\n",
    "    port1=wg1.ports[\"o2\"],\n",
    "    port2=wg2.ports[\"o1\"],\n",
    "    cross_section=gf.cross_section.strip(width=3.2, radius=100),\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "53",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "\n",
    "# Create two multi-port components.\n",
    "comp1 = c << gf.components.nxn(west=0, east=10, xsize=10, ysize=100, wg_width=3.2)\n",
    "comp2 = c << gf.components.nxn(west=0, east=10, xsize=10, ysize=100, wg_width=3.2)\n",
    "\n",
    "# Position the second component.\n",
    "comp2.rotate(30)\n",
    "comp2.move((500, -100))\n",
    "\n",
    "# Route between corresponding ports.\n",
    "for i in range(10):\n",
    "    port1_name = f\"o{10-i}\"  # Inverted port id for port1.\n",
    "    port2_name = f\"o{i+1}\"  # Adjusted to match available ports.\n",
    "    gf.routing.route_dubins(\n",
    "        c,\n",
    "        port1=comp1.ports[port1_name],\n",
    "        port2=comp2.ports[port2_name],\n",
    "        cross_section=gf.cross_section.strip(width=3.2, radius=100 + i * 10),\n",
    "    )\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "54",
   "metadata": {},
   "source": [
    "## auto_taper\n",
    "\n",
    "Both `route_single` and `route_bundle` have a `auto_taper` parameter. \n",
    "\n",
    "For auto_taper to work you need to define how to transition between different layers and widths. Another option is to explicitly pass the `auto_taper_taper` parameter to `route_bundle`.\n",
    "See examples in the next section.\n",
    "\n",
    "```python\n",
    "\n",
    "layer_transitions = {\n",
    "    LAYER.WG: partial(gf.c.taper, cross_section=\"strip\", length=10),\n",
    "    (LAYER.WG, LAYER.WGN): \"taper_sc_nc\",\n",
    "    (LAYER.WGN, LAYER.WG): \"taper_nc_sc\",\n",
    "    LAYER.M3: \"taper_electrical\",\n",
    "}\n",
    "\n",
    "return Pdk(\n",
    "    name=\"generic\",\n",
    "    cells=cells,\n",
    "    cross_sections=cross_sections,\n",
    "    layers=LAYER,\n",
    "    layer_stack=LAYER_STACK,\n",
    "    layer_views=LAYER_VIEWS,\n",
    "    layer_transitions=layer_transitions,\n",
    "    materials_index=materials_index,\n",
    "    constants=constants,\n",
    "    connectivity=LAYER_CONNECTIVITY,\n",
    ")\n",
    "\n",
    "```\n",
    "\n",
    "As seen in the code below: \n",
    "If you have a width mismatch between two ports, the router will automatically add a taper to transition between the two widths, only if `auto_taper=True`, otherwise it will raise an error.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "55",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "s1 = c << gf.components.straight()\n",
    "s2 = c << gf.components.straight(width=2)\n",
    "s2.move((40, 50))\n",
    "route = gf.routing.route_single(\n",
    "    c,\n",
    "    port1=s1.ports[\"o2\"],\n",
    "    port2=s2.ports[\"o1\"],\n",
    "    cross_section=\"strip\",\n",
    "    auto_taper=True,\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "56",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "from gdsfactory.routing.auto_taper import auto_taper_to_cross_section\n",
    "\n",
    "\n",
    "@gf.cell\n",
    "def silicon_nitride_strip(width_nitride: float = 1) -> gf.Component:\n",
    "    c = gf.Component()\n",
    "    ref = c << gf.c.straight(\n",
    "        cross_section=gf.cross_section.nitride, width=width_nitride\n",
    "    )\n",
    "    port1 = auto_taper_to_cross_section(\n",
    "        c, port=ref[\"o1\"], cross_section=gf.cross_section.strip\n",
    "    )\n",
    "    c.add_port(name=\"o1\", port=port1)\n",
    "    c.add_port(name=\"o2\", port=ref[\"o2\"])\n",
    "    return c\n",
    "\n",
    "\n",
    "c = silicon_nitride_strip(width_nitride=1)\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "57",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = silicon_nitride_strip(width_nitride=4)\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "58",
   "metadata": {},
   "source": [
    "## route_bundle with tapers\n",
    "\n",
    "You can automatically taper the routes to match the width of the ports of the route width cross_section."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "59",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "s1 = c << gf.components.straight(width=4)\n",
    "s2 = c << gf.components.straight(width=4)\n",
    "s2.move((100, 50))\n",
    "route = gf.routing.route_bundle(\n",
    "    c, [s1.ports[\"o2\"]], [s2.ports[\"o1\"]], auto_taper=True, cross_section=\"strip\"\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60",
   "metadata": {},
   "source": [
    "You can also pass a particular taper to the route_bundle function to automatically add tapers to the routes when needed:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "61",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "s1 = c << gf.components.straight(width=4)\n",
    "s2 = c << gf.components.straight(width=4)\n",
    "s2.move((100, 50))\n",
    "route = gf.routing.route_bundle(\n",
    "    c, [s1.ports[\"o2\"]], [s2.ports[\"o1\"]], auto_taper=True, cross_section=\"strip\", auto_taper_taper=gf.components.taper(width1=4, width2=0.5, length=30)\n",
    ")\n",
    "c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "62",
   "metadata": {},
   "source": [
    "## route_bundle with low loss\n",
    "\n",
    "You can taper to wider widths to reduce the propagation loss, as wider widths have lower loss."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "63",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "mmi1 = c << gf.components.mmi1x2()\n",
    "mmi2 = c << gf.components.mmi1x2()\n",
    "mmi2.move((100, 50))\n",
    "route = gf.routing.route_bundle(\n",
    "    c, [mmi1.ports[\"o2\"]], [mmi2.ports[\"o1\"]], taper=gf.components.taper(width1=0.5, width2=2), cross_section=\"strip\", min_straight_taper=20,\n",
    ")\n",
    "c"
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "custom_cell_magics": "kql"
  },
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
